package com.tong.cloud.redisson.lock.inspect;

import com.tong.cloud.redisson.lock.annotation.Lock;
import com.tong.cloud.redisson.lock.exception.LockException;
import com.tong.cloud.redisson.lock.properties.RedissonProperties;
import com.tong.cloud.redisson.lock.enums.LockType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;


@Slf4j
@Aspect
@Order(-10)
public class LockInspector {
    @Autowired
    private RedissonProperties redissonProperties;

    @Autowired
    private RedissonClient redissonClient;

    @Pointcut("@annotation(lock)")
    public void controllerAspect(Lock lock) {

    }

    public List<String> getValueBySpel(String key, String[] paramterNames, Object[] values, String keyConstant) {
        List<String> keys = new ArrayList<>();
        if (!key.contains("#")) {
            String s = "redisson:lock:" + key + keyConstant;
            log.info("no spel express value->{}", s);
            keys.add(s);
            return keys;
        }
        //spel parser
        ExpressionParser parser = new SpelExpressionParser();
        //spel context
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paramterNames.length; i++) {
            context.setVariable(paramterNames[i], values[i]);
        }
        Expression expression = parser.parseExpression(key);
        Object value = expression.getValue(context);
        if (value != null) {
            if (value instanceof List) {
                List value1 = (List) value;
                for (Object o : value1) {
                    keys.add("redisson:lock:" + o.toString() + keyConstant);
                }
            } else if (value.getClass().isArray()) {
                Object[] obj = (Object[]) value;
                for (Object o : obj) {
                    keys.add("redisson:lock:" + o.toString() + keyConstant);
                }
            } else {
                keys.add("redisson:lock:" + value.toString() + keyConstant);
            }
        }
        log.info("spel express key={},value={}", key, keys);
        return keys;
    }

    @Around("controllerAspect(lock)")
    public Object arroundAdvice(ProceedingJoinPoint point, Lock lock) throws Throwable {
        String[] keys = lock.keys();
        if (keys.length == 0) {
            throw new RuntimeException("keys not empty");
        }
        String[] parameterNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(((MethodSignature) point.getSignature()).getMethod());
        Object[] args = point.getArgs();
        long attemptTimeout = lock.attemptTimeout();
        if (attemptTimeout == 0) {
            attemptTimeout = redissonProperties.getAttemptTimeout();
        }
        long lockWatchdogTimeout = lock.lockWatchdogTimeout();
        if (lockWatchdogTimeout == 0) {
            lockWatchdogTimeout = redissonProperties.getLockWatchdogTimeout();
            ;
        }
        LockType lockType = lock.lockType();
        if (lockType == LockType.AUTO) {
            //如果系统有配置则取配置
            LockType type = redissonProperties.getLockType();
            if (type != null) {
                lockType = type;
            } else if (keys.length > 1) {
                lockType = lockType.REDLOCK;
            } else {
                lockType = LockType.REENTRANT;
            }
        }
        if (!lockType.equals(LockType.MULTIPLE) && !lockType.equals(LockType.REDLOCK) && keys.length > 1) {
            throw new RuntimeException("参数有多个,锁模式为->" + lockType.name() + ".无法锁定");
        }
        log.info("锁模式->{},等待锁定时间->{}秒.锁定最长时间->{}秒", lockType.name(), attemptTimeout / 1000, lockWatchdogTimeout / 1000);
        boolean res = false;
        RLock rLock = null;
        switch (lockType) {
            case FAIR:
                rLock = redissonClient.getFairLock(getValueBySpel(keys[0], parameterNames, args, lock.keyConstant()).get(0));
                break;
            case REDLOCK:
                List<RLock> rLocks = new ArrayList<>();
                for (String key : keys) {
                    List<String> vauleBySpel = getValueBySpel(key, parameterNames, args, lock.keyConstant());
                    for (String s : vauleBySpel) {
                        rLocks.add(redissonClient.getLock(s));
                    }
                }
                RLock[] locks = new RLock[rLocks.size()];
                int index = 0;
                for (RLock r : rLocks) {
                    locks[index++] = r;
                }
                rLock = new RedissonRedLock(locks);
                break;
            case MULTIPLE:
                rLocks = new ArrayList<>();

                for (String key : keys) {
                    List<String> vauleBySpel = getValueBySpel(key, parameterNames, args, lock.keyConstant());
                    for (String s : vauleBySpel) {
                        rLocks.add(redissonClient.getLock(s));
                    }
                }
                locks = new RLock[rLocks.size()];
                index = 0;
                for (RLock r : rLocks) {
                    locks[index++] = r;
                }
                rLock = new RedissonMultiLock(locks);
                break;
            case REENTRANT:
                List<String> vauleBySpel = getValueBySpel(keys[0], parameterNames, args, lock.keyConstant());
                //如果spel表达式是数组或者LIST 则使用红锁
                if (vauleBySpel.size() == 1) {
                    rLock = redissonClient.getLock(vauleBySpel.get(0));
                } else {
                    locks = new RLock[vauleBySpel.size()];
                    index = 0;
                    for (String s : vauleBySpel) {
                        locks[index++] = redissonClient.getLock(s);
                    }
                    rLock = new RedissonRedLock(locks);
                }
                break;
            case READ:
                RReadWriteLock rwlock = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, lock.keyConstant()).get(0));
                rLock = rwlock.readLock();
                break;
            case WRITE:
                RReadWriteLock rwlock1 = redissonClient.getReadWriteLock(getValueBySpel(keys[0], parameterNames, args, lock.keyConstant()).get(0));
                rLock = rwlock1.writeLock();
                break;
        }
        //执行aop
        if (rLock != null) {
            try {
                if (attemptTimeout == -1) {
                    res = true;
                    //一直等待加锁
                    rLock.lock(lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                } else {
                    res = rLock.tryLock(attemptTimeout, lockWatchdogTimeout, TimeUnit.MILLISECONDS);
                }
                if (res) {
                    Object obj = point.proceed();
                    return obj;
                } else {
                    throw new LockException("获取锁失败");
                }
            } finally {
                if (res) {
                    rLock.unlock();
                }
            }
        }
        throw new LockException("try redisson lock exception");
    }

}
